package syscall

import "unsafe"
import "errors"

type SockTypeT _sock_type_t

type AddressFamily uint16
type ProtoFamily uint16

type IPProtocol uint16

type SockAddr interface{}

type SockAddrIn struct {
	Family  uint16
	Port    uint16
	Addr    [4]byte
	padding [8]byte
}

type SockAddrIn6 struct {
	Family   uint16
	Port     uint16
	FlowInfo uint32
	Addr     [16]byte
	ScopeID  uint32
}

const (
	AF_UNKNOWN = AddressFamily(_AF_UNKNOWN)
	AF_INET    = AddressFamily(_AF_INET)  /* IPv4 address */
	AF_INET6   = AddressFamily(_AF_INET6) /* IPv6 address */
)

const (
	PF_INET  = ProtoFamily(AF_INET)
	PF_INET6 = ProtoFamily(AF_INET6)
)

const (
	SOCK_STREAM = SockTypeT(_SOCK_STREAM)
	SOCK_DGRAM  = SockTypeT(_SOCK_DGRAM)
	SOCK_RAW    = SockTypeT(_SOCK_RAW)
)

const (
	IPPROTO_ICMP = IPProtocol(_IPPROTO_ICMP)
	IPPROTO_TCP  = IPProtocol(_IPPROTO_TCP)
	IPPROTO_UDP  = IPProtocol(_IPPROTO_UDP)
)

func unpackAddress(addr SockAddr) (unsafe.Pointer, _socklen_t) {
	switch a := addr.(type) {
	case SockAddrIn:
		return unsafe.Pointer(&a), _socklen_t(_sizeof_sockaddr_in)
	case *SockAddrIn:
		return unsafe.Pointer(a), _socklen_t(_sizeof_sockaddr_in)
	case SockAddrIn6:
		return unsafe.Pointer(&a), _socklen_t(_sizeof_sockaddr_in6)
	case *SockAddrIn6:
		return unsafe.Pointer(a), _socklen_t(_sizeof_sockaddr_in6)
	}
	return nil, 0
}

//extern socket
func socket(domain int, typ int, protocol int) int

func Socket(domain ProtoFamily, typ SockTypeT, proto IPProtocol) (int, error) {
	rc := socket(int(domain), int(typ), int(proto))
	if rc < 0 {
		return -1, Errno(rc)
	}
	return rc, nil
}

//extern bind
func bind(socket_id int, my_addr unsafe.Pointer, addrlen _socklen_t) int

func Bind(socket_id int, my_addr SockAddr) error {
	addr, alen := unpackAddress(my_addr)
	if addr == nil {
		return errors.New("Invalid type or my_addr.")
	}

	if rc := bind(socket_id, addr, alen); rc != 0 {
		return Errno(rc)
	}
	return nil
}

//extern listen
func listen(socket_id int, backlog int) int

func Listen(socket_id int, backlog int) error {
	if rc := listen(socket_id, backlog); rc != 0 {
		return Errno(rc)
	}
	return nil
}

//extern accept
func accept(socket_id int, client_addr unsafe.Pointer, addrlen *_socklen_t) int

func Accept(socket_id int) (client_socket int, client_addr SockAddr, e error) {
	addr := new([_sizeof_sockaddr_in6]byte) // Assume IPv6 is the largest possible case.
	addrsize := _socklen_t(_sizeof_sockaddr_in6)

	rc := accept(socket_id, unsafe.Pointer(addr), &addrsize)
	if rc < 0 {
		return 0, nil, Errno(rc)
	}

	switch AddressFamily(((*_sockaddr_t)(unsafe.Pointer(addr))).sa_family) {
	case AF_INET:
		return rc, (*SockAddrIn)(unsafe.Pointer(addr)), nil
	case AF_INET6:
		return rc, (*SockAddrIn6)(unsafe.Pointer(addr)), nil
	}
	return 0, nil, errors.New("System returned invalid address.")
}

//extern connect
func connect(socket_id int, serv_addr unsafe.Pointer, addrlen _socklen_t) int

func Connect(socket_id int, serv_addr SockAddr) error {
	addr, alen := unpackAddress(serv_addr)
	if addr == nil {
		return errors.New("Invalid type or serv_addr.")
	}

	if rc := connect(socket_id, addr, alen); rc != 0 {
		return Errno(rc)
	}
	return nil
}

//extern closesocket
func closesocket(socket_id int) int

func CloseSocket(socket_id int) error {
	if rc := closesocket(socket_id); rc != 0 {
		return Errno(rc)
	}
	return nil
}

// FIXME: send probably has bad documentation

//extern send
func send(socket_id int, data *byte, datalength _size_t, flags int) int

func Send(socket_id int, data []byte, flags int) (int, error) {
	if rc := send(socket_id, &data[0], _size_t(len(data)), flags); rc != 0 {
		return 0, Errno(rc)
	}
	return len(data), nil
}

//extern sendto
func sendto(socket_id int, data *byte, datalength _size_t, flags int, toaddr unsafe.Pointer, addrlen _socklen_t) int

func SendTo(socket_id int, data []byte, flags int, toaddr SockAddr) (int, error) {
	addr, alen := unpackAddress(toaddr)
	if addr == nil {
		return 0, errors.New("Invalid argument 'toaddr'.")
	}

	if rc := sendto(socket_id, &data[0], _size_t(len(data)), flags, addr, alen); rc != 0 {
		return 0, Errno(rc)
	}
	return len(data), nil
}

//extern recv
func recv(socket_id int, data *byte, datalength _size_t, flags int) _ssize_t

func Recv(socket_id int, data []byte, flags int) (int, error) {
	rc := recv(socket_id, &data[0], _size_t(len(data)), flags)

	if rc < 0 {
		return -1, Errno(rc)
	}
	return int(rc), nil
}

//extern recvfrom
func recvfrom(socket_id int, data *byte, datalength _size_t, flags int, fromaddr unsafe.Pointer, addrlen *_socklen_t) _ssize_t

func RecvFrom(socket_id int, data []byte, flags int) (bytesread int, fromaddr SockAddr, e error) {
	addr := new([_sizeof_sockaddr_in6]byte) // Assume IPv6 is the largest possible case.
	addrsize := _socklen_t(_sizeof_sockaddr_in6)

	rc := recvfrom(socket_id, &data[0], _size_t(len(data)), flags, unsafe.Pointer(addr), &addrsize)
	if rc < 0 {
		return -1, nil, Errno(rc)
	}

	switch AddressFamily(((*_sockaddr_t)(unsafe.Pointer(addr))).sa_family) {
	case AF_INET:
		return int(rc), (*SockAddrIn)(unsafe.Pointer(addr)), nil
	case AF_INET6:
		return int(rc), (*SockAddrIn6)(unsafe.Pointer(addr)), nil
	}
	return -1, nil, errors.New("System returned invalid address.")
}

// TODO: socket options
